package services

import (
	"context"
	"errors"
	"go-mp-starter/global"
	"go-mp-starter/utils"
	"strconv"
	"time"

	"github.com/dgrijalva/jwt-go"
)

type jwtService struct{}

// JwtService jwt服务实例
var JwtService = new(jwtService)

// 所有需要颁发jwt的用户模型必须要实现这个接口
type JwtUser interface {
	GetUid() string
}

// CustomClaims 自定义claims
type CustomClaims struct {
	jwt.StandardClaims
}

const (
	TokenType    = "bearer"
	AppGuardName = "app"
)

type TokenOutput struct {
	AccessToken string `json:"access_token"`
	ExpiresIn   int    `json:"expires_in"`
	TokenType   string `json:"token_type"`
}

// CreateToken 生成token
func (jwtService *jwtService) CreateToken(GuardName string, user JwtUser) (tokenData TokenOutput, token *jwt.Token, err error) {
	token = jwt.NewWithClaims(
		jwt.SigningMethodHS256,
		CustomClaims{
			StandardClaims: jwt.StandardClaims{
				ExpiresAt: time.Now().Unix() + global.App.Config.Jwt.JwtTtl,
				Id:        user.GetUid(),
				Issuer:    GuardName, // 区分不同客户端发的token, 避免token跨端使用
				NotBefore: time.Now().Unix() - 1000,
			},
		},
	)
	tokenStr, err := token.SignedString([]byte(global.App.Config.Jwt.Secret))
	tokenData = TokenOutput{
		tokenStr,
		int(global.App.Config.Jwt.JwtTtl),
		TokenType,
	}
	return
}

// getBlackListKey 获取黑名单缓存key
func (js *jwtService) getBlackListKey(tokenStr string) string {
	return "jwt_blacklist_key:" + utils.MD5([]byte(tokenStr))
}

// JoinBlackList 将token加入黑名单
func (js *jwtService) JoinBlackList(token *jwt.Token) (err error) {
	nowUnix := time.Now().Unix()
	// token剩余的时间
	timer := time.Duration(token.Claims.(*CustomClaims).ExpiresAt-nowUnix) * time.Second
	// 将token剩余的时间作为缓存的有效期, 并将当前时间作为缓存 value值
	err = global.App.Redis.SetNX(context.Background(), js.getBlackListKey(token.Raw), nowUnix, timer).Err()
	return
}

// IsInBlackList token是否在黑名单中
func (js *jwtService) IsInBlackList(tokenStr string) bool {
	joinUnixStr, err := global.App.Redis.Get(context.Background(), js.getBlackListKey(tokenStr)).Result()
	if joinUnixStr == "" || err != nil {
		return false
	}
	joinUnix, err := strconv.Atoi(joinUnixStr)
	if err != nil {
		return false
	}
	// JwtBlackListGracePeriod 黑名单宽限时间, 避免并发请求失败
	return time.Now().Unix() > int64(joinUnix)+global.App.Config.Jwt.JwtBlackListGracePeriod
}

// GetUserInfo 获取用户信息
func (js *jwtService) GetUserInfo(GuardName string, id string) (user JwtUser, err error) {
	switch GuardName {
	case AppGuardName:
		return UserService.GetUserInfo(id)
	default:
		err = errors.New("guardName " + GuardName + " does not exists")
	}
	return
}
